﻿
// **************************************************************************
//    Class Created by Mick Doherty (Dotnetrix) June 2010
//    http://dotnetrix.co.uk/
//
//    ...for GlassUI (an AeroGlass UI Library)
//    http://glassui.codeplex.com/
// **************************************************************************

using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using GlassUI.Design;

namespace GlassUI
{
    [DefaultEvent("Scroll")]
    [DefaultProperty("Value")]
    [ToolboxBitmap(typeof(System.Windows.Forms.TrackBar))]
    public class TrackBar : GlassControlBase
    {
        public TrackBar()
            : base()
        {
            this.SetStyle(ControlStyles.SupportsTransparentBackColor | ControlStyles.OptimizedDoubleBuffer |
                          ControlStyles.AllPaintingInWmPaint | ControlStyles.Selectable | ControlStyles.UserMouse, true);
            this.Thumb = new TrackBarThumb(this);
            this.LayoutTrackBarParts();
        }

        protected override Size DefaultSize
        {
            get
            {
                return new Size(100, 46);
            }
        }

        #region Event Delarations

        public event EventHandler Scroll;
        public event EventHandler ValueChanged;

        #endregion

        #region Private Instance Variables

        private Orientation orientation = Orientation.Horizontal;

        private int minimum;
        private int maximum = 10;
        private int smallChange = 1;
        private int largeChange = 5;
        private int _value;

        private TickStyle tickStyle = TickStyle.BottomRight;
        private int tickFrequency = 1;

        internal bool thumbDragging;
        private bool scrollUp;

        private TrackBarThumb Thumb;
        private Timer scrollTimer;

        private Color thumbColor = SystemColors.Control;

        #endregion

        #region Public properties

        [Browsable(false), EditorBrowsable( EditorBrowsableState.Never)]
        public override Image BackgroundImage
        {
            get
            {
                return base.BackgroundImage;
            }
            set
            {
                base.BackgroundImage = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public override ImageLayout BackgroundImageLayout
        {
            get
            {
                return base.BackgroundImageLayout;
            }
            set
            {
                base.BackgroundImageLayout = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public override int CornerRadius
        {
            get
            {
                return base.CornerRadius;
            }
            set
            {
                base.CornerRadius = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public override Corners RoundedCorners
        {
            get
            {
                return base.RoundedCorners;
            }
            set
            {
                base.RoundedCorners = value;
            }
        }

        [DefaultValue(typeof(Orientation), "Horizontal")]
        [GlassUIDescription("TrackBarOrientationDescr", "System.Windows.Forms")]
        public Orientation Orientation
        {
            get { return this.orientation; }
            set
            {
                if (this.orientation != value)
                {
                    this.orientation = value;
                    int w, h;
                    w = this.Height;
                    h = this.Width;
                    this.Size = new Size(w, h);
                    this.LayoutTrackBarParts();
                    this.Invalidate();
                }
            }
        }

        [DefaultValue(0)]
        [RefreshProperties(RefreshProperties.All)]
        [GlassUIDescription("TrackBarMinimumDescr", "System.Windows.Forms")]
        public int Minimum
        {
            get { return minimum; }
            set
            {
                if (minimum == value)
                    return;
                minimum = value;
                if (maximum <= value)
                    Maximum = value;
                this.LayoutTrackBarParts();
                this.Invalidate();
            }
        }

        [DefaultValue(10)]
        [GlassUIDescription("TrackBarMaximumDescr", "System.Windows.Forms")]
        public int Maximum
        {
            get { return maximum; }
            set
            {
                if (maximum == value)
                    return;
                maximum = value;
                if (minimum >= value)
                    Minimum = value;
                this.LayoutTrackBarParts();
                this.Invalidate();
            }
        }

        [DefaultValue(1)]
        [GlassUIDescription("TrackBarSmallChangeDescr", "System.Windows.Forms")]
        public int SmallChange
        {
            get { return smallChange; }
            set
            {
                if (smallChange == value)
                    return;
                smallChange = value;
            }
        }

        [DefaultValue(5)]
        [GlassUIDescription("TrackBarLargeChangeDescr", "System.Windows.Forms")]
        public int LargeChange
        {
            get { return largeChange; }
            set
            {
                if (largeChange == value)
                    return;
                largeChange = value;
            }
        }

        [GlassUIDescription("TrackBarValueDescr", "System.Windows.Forms")]
        public int Value
        {
            get
            {
                if (_value < this.minimum)
                    return this.minimum;
                return _value;
            }
            set
            {
                if (value < this.minimum)
                    value = this.minimum;
                if (value > this.maximum)
                    value = this.maximum;
                if (value == _value)
                    return;
                _value = value;
                this.LayoutTrackBarParts();
                this.OnValueChanged(EventArgs.Empty);
            }
        }

        private bool ShouldSerializeValue()
        {
            return this._value != this.minimum;
        }
        private void ResetValue()
        {
            this._value = this.minimum;
        }

        [DefaultValue(false)]
        public new bool TabStop
        {
            get { return base.TabStop; }
            set { base.TabStop = value; }
        }

        [DefaultValue("")]
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override string Text
        {
            get { return ""; }
            set { base.Text = ""; }
        }

        [DefaultValue(typeof(TickStyle), "BottomRight")]
        [GlassUIDescription("TrackBarTickStyleDescr", "System.Windows.Forms")]
        public TickStyle TickStyle
        {
            get { return this.tickStyle; }
            set
            {
                if (this.tickStyle == value)
                    return;
                this.tickStyle = value;
                this.Invalidate();
            }
        }

        [DefaultValue(1)]
        [GlassUIDescription("TrackBarTickFrequencyDescr", "System.Windows.Forms")]
        public int TickFrequency
        {
            get { return this.tickFrequency; }
            set
            {
                if (this.tickFrequency == value)
                    return;
                this.tickFrequency = value;
                if (this.TickStyle != TickStyle.None)
                    this.Invalidate();
            }
        }

        [DefaultValue(typeof(Color), "Control")]
        [GlassUIDescription("TrackBarThumbColorDescription")]
        public Color ThumbColor
        {
            get 
            { 
                if(Thumb == null)
                    return thumbColor;
                return Thumb.color;
            }
            set
            {
                if (thumbColor != value)
                {
                    thumbColor = value;
                    if (Thumb != null)
                        Thumb.color = thumbColor;
                    this.Invalidate();
                }
            }
        }

        #endregion

        #region private Properties

        private bool Horizontal
        {
            get { return this.orientation == Orientation.Horizontal; }
        }

        #endregion

        #region Overridden Methods

        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
            base.OnPaintBackground(pevent);

            DrawTicks(pevent.Graphics);

            pevent.Graphics.SmoothingMode = SmoothingMode.HighQuality;
            pevent.Graphics.CompositingQuality = CompositingQuality.HighQuality;

            DrawTrack(pevent.Graphics);

            Thumb.Draw(pevent.Graphics);

            //Draw Focus
            if (this.Focused && this.ShowFocusCues)
                ControlPaint.DrawFocusRectangle(pevent.Graphics, this.ClientRectangle);
        }

        protected override void OnGotFocus(EventArgs e)
        {
            base.OnGotFocus(e);
            if (Thumb != null)
                Thumb.ChangeFocus(this.Focused && this.ShowFocusCues);
            this.Invalidate();
        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);
            if (Thumb != null)
                Thumb.ChangeFocus(this.Focused && this.ShowFocusCues);
            this.Invalidate();
        }

        protected override bool IsInputKey(Keys keyData)
        {
            switch (keyData)
            {
                case Keys.Up:
                case Keys.Down:
                case Keys.Left:
                case Keys.Right:
                    return true;
                default:
                    return base.IsInputKey(keyData);
            }
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            switch (e.KeyCode)
            {
                case Keys.Up:
                case Keys.Left:
                    this.Value += this.Horizontal ? -this.smallChange : this.smallChange;
                    break;
                case Keys.Down:
                case Keys.Right:
                    this.Value += this.Horizontal ? this.smallChange : -this.smallChange;
                    break;
                case Keys.PageDown:
                    this.Value += this.Horizontal ? this.largeChange : -this.largeChange;
                    break;
                case Keys.PageUp:
                    this.Value += this.Horizontal ? -this.largeChange : this.largeChange;
                    break;
                case Keys.Home:
                    this.Value = this.Horizontal ? this.minimum : this.maximum;
                    break;
                case Keys.End:
                    this.Value = this.Horizontal ? this.maximum : this.minimum;
                    break;
            }
        }

        protected override void OnMouseWheel(MouseEventArgs e)
        {
            base.OnMouseWheel(e);
            this.Value += (e.Delta / WHEEL_DELTA) * WHEEL_LINES;
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            //if (this.CanSelect)
            //    this.Select();

            if (e.Button == MouseButtons.Left)
            {
                thumbDragging = Thumb.HitTest(e.Location);

                if (!thumbDragging)
                {
                    scrollUp = this.Horizontal ? e.X > Thumb.Right : e.Y < Thumb.Top;
                    if (scrollTimer == null)
                        InitTimer();
                    scrollTimer.Start();
                }

            }

        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            thumbDragging = false;
            if (this.scrollTimer != null)
                this.scrollTimer.Stop();
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (thumbDragging)
            {
                this.Value = ValueFromPoint(e.Location);
            }

        }

        protected override void OnVisibleChanged(EventArgs e)
        {
            base.OnVisibleChanged(e);
            if (this.Visible)
                this.LayoutTrackBarParts();
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            this.LayoutTrackBarParts();
        }

        #endregion

        #region Protected Methods

        protected virtual void OnScroll(EventArgs e)
        {
            if (this.Scroll != null)
                this.Scroll(this, e);
        }

        protected virtual void OnValueChanged(EventArgs eventArgs)
        {
            if (this.ValueChanged != null)
                this.ValueChanged(this, eventArgs);
            this.LayoutTrackBarParts();
            this.OnScroll(eventArgs);
            this.Invalidate();
        }

        #endregion

        #region Internal Methods

        private void LayoutTrackBarParts()
        {
            if (Thumb == null)
                return;

            Thumb.Size = this.Horizontal ?
                new Size(10, 20) :
                new Size(20, 10);

            float channelLength = this.Horizontal ?
                this.Width - 22 : // Channel Left margin + Channel Right margin + Thumb.Width
                this.Height - 22; // Channel Top margin + Channel Bottom margin + Thumb.Height

            float stepCount = (this.Maximum - this.Minimum);

            float stepSize = stepCount > 0 ? channelLength / stepCount : 0;

            float thumbOffset = (stepSize) * (this.Value - this.minimum);

            Thumb.Location = this.Horizontal ?
                Point.Round(new PointF(6 + thumbOffset, this.Height / 2 - 10)) :
                Point.Round(new PointF(this.Width / 2 - 10, channelLength - thumbOffset + 6));

        }

        [SuppressMessage("Microsoft.Mobility", "CA1601:DoNotUseTimersThatPreventPowerStateChanges")]
        private void InitTimer()
        {
            this.scrollTimer = new Timer();
            this.scrollTimer.Interval = 500;
            this.scrollTimer.Tick += new EventHandler(scrollTimer_Tick);
        }

        private void scrollTimer_Tick(object sender, EventArgs e)
        {
            this.Value += this.scrollUp ? this.largeChange : -this.largeChange;

            if (this._value == this.minimum || this._value == this.maximum)
                this.scrollTimer.Stop();

            int val = this.ValueFromPoint(this.PointToClient(Cursor.Position));

            if (this.scrollUp && this._value > val)
                this.scrollTimer.Stop();

            if (!this.scrollUp && this._value < val)
                this.scrollTimer.Stop();
        }

        private int ValueFromPoint(Point point)
        {
            float channelLength = this.Horizontal ?
                this.Width - 26 : // Channel Left margin + Channel Right margin + Thumb.Width
                this.Height - 26; // Channel Top margin + Channel Bottom margin + Thumb.Height

            float stepCount = (this.maximum - this.minimum);

            float stepSize = stepCount > 0 ? channelLength / stepCount : 0;

            if (this.Horizontal)
            {
                point.Offset(-7, 0);
                return (int)(point.X / stepSize) + this.minimum;
            }
            point.Offset(0, -7);
            return this.maximum - (int)(point.Y / stepSize) + this.minimum;
        }

        private void DrawTicks(Graphics graphics)
        {
            if (tickStyle == TickStyle.None)
                return;

            int positionCount = ((this.Maximum - this.Minimum));

            int lineOffset = TickFrequency != 0 ? Math.Abs(TickFrequency) : positionCount;
            int tickCount = positionCount / lineOffset;

            tickCount++;

            bool horizontal = this.Horizontal;

            int xOffset = horizontal ? this.Thumb.Width / 2 : 0;
            int yOffset = horizontal ? 0 : this.Thumb.Height / 2;

            RectangleF tickBounds = new Rectangle(6, 6, this.Width - 12, this.Height - 12);

            tickBounds.Inflate(-xOffset, -yOffset);

            float gap = horizontal ? (tickBounds.Width / positionCount) * lineOffset :
                (tickBounds.Height / positionCount) * lineOffset;

            Color dark = Color.FromArgb(128, Color.Black);
            Color light = Color.FromArgb(128, Color.White);

            for (int id = 0; id <= tickCount; id++)
            {
                if (horizontal)
                {
                    float x = Math.Min((gap * id) + tickBounds.X, tickBounds.Right);

                    for (int count = 0; count < 2; count++)
                    {
                        if (count == 1)
                            x++;
                        using (Pen pen = new Pen(count == 0 ? dark : light))
                        {
                            if (((int)this.TickStyle & 1) == 1)
                                graphics.DrawLine(pen, new PointF(x, tickBounds.Top), new PointF(x, Thumb.Top - 3));
                            if (((int)this.TickStyle >= 2))
                                graphics.DrawLine(pen, new PointF(x, Thumb.Bottom + 3), new PointF(x, tickBounds.Bottom));
                        }
                    }
                }
                else
                {
                    float y = Math.Min((gap * id) + tickBounds.Y, tickBounds.Bottom);

                    for (int count = 0; count < 2; count++)
                    {
                        if (count == 1)
                            y++;
                        using (Pen pen = new Pen(count == 0 ? dark : light))
                        {
                            if (((int)this.TickStyle & 1) == 1)
                                graphics.DrawLine(pen, new PointF(tickBounds.X, y), new PointF(Thumb.Left - 3, y));
                            if (((int)this.TickStyle >= 2))
                                graphics.DrawLine(pen, new PointF(Thumb.Right + 3, y), new PointF(tickBounds.Right, y));
                        }

                    }
                }
            }


        }

        private void DrawTrack(Graphics graphics)
        {
            Rectangle channelBounds = this.Horizontal ?
               new Rectangle(6, this.Height / 2 - 2, this.Width - 13, 4) :
               new Rectangle(this.Width / 2 - 2, 6, 4, this.Height - 13);

            graphics.SetClip(this.Thumb.Bounds, CombineMode.Exclude);

            using (GraphicsPath path = GlassUtilities.RoundedRectangle(channelBounds, 3))
            {
                using (Brush brush = new SolidBrush(Color.FromArgb(128, Color.White)))
                    graphics.FillPath(brush, path);
                using (Pen pen = new Pen(Color.FromArgb(196, Color.Black)))
                    graphics.DrawPath(pen, path);
            }

            graphics.ResetClip();

        }

        #endregion

        private int WHEEL_DELTA = SystemInformation.MouseWheelScrollDelta;
        private int WHEEL_LINES = SystemInformation.MouseWheelScrollLines;

        internal class TrackBarThumb : LiteControlBase
        {
            public TrackBarThumb(TrackBar owner)
                : base(owner)
            {
            }

            internal Color color = SystemColors.Control;

            public override void Draw(Graphics graphics)
            {
                Rectangle rc = this.Bounds;

                bool drawHot = this.State == ControlState.Hot || ((TrackBar)this.owner).thumbDragging;

                Color fillColor = drawHot ? this.owner.GlowColor : this.color;

                using (GraphicsPath path = GlassUtilities.RoundedRectangle(rc, 3))
                {
                    using (Brush brush = new SolidBrush(fillColor))
                        graphics.FillPath(brush, path);

                    using (Brush brush = new LinearGradientBrush(rc, Color.FromArgb(64, Color.White), Color.FromArgb(64,Color.Black), LinearGradientMode.ForwardDiagonal))
                    {
                        graphics.FillPath(brush, path);
                    }

                    using (Pen pen = new Pen(Color.FromArgb(192, Color.Black)))
                    {
                        graphics.DrawPath(pen, path);
                        rc.Inflate(-1, -1);
                        path.Reset();
                        path.AddPath(GlassUtilities.RoundedRectangle(rc, 2), false);
                        pen.Color = Color.FromArgb(192, Color.White);
                        graphics.DrawPath(pen, path);
                    }
                }
            }

            protected override void OnMouseUp(MouseEventArgs mev)
            {
                base.OnMouseUp(mev);
                this.owner.Invalidate(this.Bounds);
            }
        }

    }
}
